<template>
    <view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles"
          @click="onClick">
        <slot></slot>
    </view>
</template>

<script>
    import {createAnimation} from './createAnimation'

    /**
     * Transition 过渡动画
     * @description 简单过渡动画组件
     * @tutorial https://ext.dcloud.net.cn/plugin?id=985
     * @property {Boolean} show = [false|true] 控制组件显示或隐藏
     * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
     *  @value fade 渐隐渐出过渡
     *  @value slide-top 由上至下过渡
     *  @value slide-right 由右至左过渡
     *  @value slide-bottom 由下至上过渡
     *  @value slide-left 由左至右过渡
     *  @value zoom-in 由小到大过渡
     *  @value zoom-out 由大到小过渡
     * @property {Number} duration 过渡动画持续时间
     * @property {Object} styles 组件样式，同 css 样式，注意带’-‘连接符的属性需要使用小驼峰写法如：`backgroundColor:red`
     */
    export default {
        name: 'uniTransition',
        emits: ['click', 'change'],
        props: {
            show: {
                type: Boolean,
                default: false
            },
            modeClass: {
                type: [Array, String],
                default() {
                    return 'fade'
                }
            },
            duration: {
                type: Number,
                default: 300
            },
            styles: {
                type: Object,
                default() {
                    return {}
                }
            },
            customClass: {
                type: String,
                default: ''
            }
        },
        data() {
            return {
                isShow: false,
                transform: '',
                opacity: 1,
                animationData: {},
                durationTime: 300,
                config: {}
            }
        },
        watch: {
            show: {
                handler(newVal) {
                    if (newVal) {
                        this.open()
                    } else {
                        // 避免上来就执行 close,导致动画错乱
                        if (this.isShow) {
                            this.close()
                        }
                    }
                },
                immediate: true
            }
        },
        computed: {
            // 生成样式数据
            stylesObject() {
                let styles = {
                    ...this.styles,
                    'transition-duration': this.duration / 1000 + 's'
                }
                let transform = ''
                for (let i in styles) {
                    let line = this.toLine(i)
                    transform += line + ':' + styles[i] + ';'
                }
                return transform
            },
            // 初始化动画条件
            transformStyles() {
                return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
            }
        },
        created() {
            // 动画默认配置
            this.config = {
                duration: this.duration,
                timingFunction: 'ease',
                transformOrigin: '50% 50%',
                delay: 0
            }
            this.durationTime = this.duration
        },
        methods: {
            /**
             *  ref 触发 初始化动画
             */
            init(obj = {}) {
                if (obj.duration) {
                    this.durationTime = obj.duration
                }
                this.animation = createAnimation(Object.assign(this.config, obj), this)
            },
            /**
             * 点击组件触发回调
             */
            onClick() {
                this.$emit('click', {
                    detail: this.isShow
                })
            },
            /**
             * ref 触发 动画分组
             * @param {Object} obj
             */
            step(obj, config = {}) {
                if (!this.animation) return
                for (let i in obj) {
                    try {
                        if (typeof obj[i] === 'object') {
                            this.animation[i](...obj[i])
                        } else {
                            this.animation[i](obj[i])
                        }
                    } catch (e) {
                        console.error(`方法 ${i} 不存在`)
                    }
                }
                this.animation.step(config)
                return this
            },
            /**
             *  ref 触发 执行动画
             */
            run(fn) {
                if (!this.animation) return
                this.animation.run(fn)
            },
            // 开始过度动画
            open() {
                clearTimeout(this.timer)
                this.transform = ''
                this.isShow = true
                let {opacity, transform} = this.styleInit(false)
                if (typeof opacity !== 'undefined') {
                    this.opacity = opacity
                }
                this.transform = transform
                // 确保动态样式已经生效后，执行动画，如果不加 nextTick ，会导致 wx 动画执行异常
                this.$nextTick(() => {
                    // TODO 定时器保证动画完全执行，目前有些问题，后面会取消定时器
                    this.timer = setTimeout(() => {
                        this.animation = createAnimation(this.config, this)
                        this.tranfromInit(false).step()
                        this.animation.run()
                        this.$emit('change', {
                            detail: this.isShow
                        })
                    }, 20)
                })
            },
            // 关闭过度动画
            close(type) {
                if (!this.animation) return
                this.tranfromInit(true)
                    .step()
                    .run(() => {
                        this.isShow = false
                        this.animationData = null
                        this.animation = null
                        let {opacity, transform} = this.styleInit(false)
                        this.opacity = opacity || 1
                        this.transform = transform
                        this.$emit('change', {
                            detail: this.isShow
                        })
                    })
            },
            // 处理动画开始前的默认样式
            styleInit(type) {
                let styles = {
                    transform: ''
                }
                let buildStyle = (type, mode) => {
                    if (mode === 'fade') {
                        styles.opacity = this.animationType(type)[mode]
                    } else {
                        styles.transform += this.animationType(type)[mode] + ' '
                    }
                }
                if (typeof this.modeClass === 'string') {
                    buildStyle(type, this.modeClass)
                } else {
                    this.modeClass.forEach(mode => {
                        buildStyle(type, mode)
                    })
                }
                return styles
            },
            // 处理内置组合动画
            tranfromInit(type) {
                let buildTranfrom = (type, mode) => {
                    let aniNum = null
                    if (mode === 'fade') {
                        aniNum = type ? 0 : 1
                    } else {
                        aniNum = type ? '-100%' : '0'
                        if (mode === 'zoom-in') {
                            aniNum = type ? 0.8 : 1
                        }
                        if (mode === 'zoom-out') {
                            aniNum = type ? 1.2 : 1
                        }
                        if (mode === 'slide-right') {
                            aniNum = type ? '100%' : '0'
                        }
                        if (mode === 'slide-bottom') {
                            aniNum = type ? '100%' : '0'
                        }
                    }
                    this.animation[this.animationMode()[mode]](aniNum)
                }
                if (typeof this.modeClass === 'string') {
                    buildTranfrom(type, this.modeClass)
                } else {
                    this.modeClass.forEach(mode => {
                        buildTranfrom(type, mode)
                    })
                }

                return this.animation
            },
            animationType(type) {
                return {
                    fade: type ? 1 : 0,
                    'slide-top': `translateY(${type ? '0' : '-100%'})`,
                    'slide-right': `translateX(${type ? '0' : '100%'})`,
                    'slide-bottom': `translateY(${type ? '0' : '100%'})`,
                    'slide-left': `translateX(${type ? '0' : '-100%'})`,
                    'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
                    'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
                }
            },
            // 内置动画类型与实际动画对应字典
            animationMode() {
                return {
                    fade: 'opacity',
                    'slide-top': 'translateY',
                    'slide-right': 'translateX',
                    'slide-bottom': 'translateY',
                    'slide-left': 'translateX',
                    'zoom-in': 'scale',
                    'zoom-out': 'scale'
                }
            },
            // 驼峰转中横线
            toLine(name) {
                return name.replace(/([A-Z])/g, '-$1').toLowerCase()
            }
        }
    }
</script>

<style></style>
